﻿using System;
using PT.Common;

namespace PT.Timing {
    public class Click : IComparable<Click>, IEquatable<Click> {

        readonly Rational noteDuration;
        public Rational NoteDuration {
            get { return noteDuration; }
        }

        internal long LastClickTime { get; set; }

        readonly ClickResult clickResult;

        public Click( Rational noteDuration ) {
            if ( noteDuration.Numerator != 1 ) {
                // Support requires hits to take into account hits in alternating bars
                throw new NotSupportedException("Numerator != 1 is not yet supported.");
            }

            this.noteDuration = noteDuration;

            clickResult = new ClickResult();
        }

        /// <summary>Calculates if metronome must click current noteDuration. </summary>
        /// <returns>ClickResult structure with ActualTime of click. </returns>
        internal ClickResult CalcClick( Metronome m ) {
            if ( LastClickTime < 0 ) {
                // First click. All notes begin at the time the metronome is started, 
                // so all notes produce a click at this time
                clickResult.ActualTime = m.State.Time;
                return clickResult;
            }

            // Calculate time for next click
            // nextNote = lastNote + ( period = barPeriod / (notesPerBar = Denum / Num ) )
            var durationT = m.BarDuration * noteDuration.Numerator / noteDuration.Denominator;
            var nextClick = LastClickTime + durationT;

            // Next click in the future. Do nothing..
            if ( m.State.Time < nextClick ) {
                return ClickResult.NoClick;
            }

            // Time for next click or click event already in the past
            clickResult.ActualTime = nextClick;

            if ( nextClick + durationT < m.State.Time ) {
                // The calculated click is not the last one - a click has been skipped
                // The handling behaviour in the future could be based on policy, for example
                // AnnounceAll, SkipLateClick or RaiseException. For now, raise exception.
                throw new Exception( "Skipped click. Timer cannot keep up.");
            }

            return clickResult;
        }

        public bool Equals( Click other ) {
            return CompareTo( other ) == 0;
        }

        public override int GetHashCode() {
            return NoteDuration.GetHashCode();
        }

        public int CompareTo( Click other ) {
            return NoteDuration.CompareTo( other.NoteDuration );
        }

    }
}
